package thread;

//自实现一个 定时器

//对于 自定义任务 我们有两种写法
//1、基于使用 抽象类abstract 来定义 MyTimerTask 来实现 Runnable接口，重写 run 方法
//但是这样编写的 MyTimerTask这个类呢就是有点麻烦了
//abstract class MyTimerTask implements Runnable{
//    @Override
//    public abstract void run();//这里同样要定义为抽象方法，我们在调用 run 方法的调用者中再重写 run方法
//}
//上述的方法有点麻烦，我们呢使用另一种方法

//2、我们直接定义 MyTimerTask这个类，对于这个 Runnable这个 任务类呢就直接实现实例，不使用实现接口的方法

import java.util.PriorityQueue;
//任务
class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;//任务
    //定时器还有执行任务的时间
    private long time;

    public MyTimerTask(Runnable task,long time) {
        this.task = task;
        this.time = time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
        //return (int) (o.time - this.time);//这两个比较规则都有可能，我们最好的方法就是进行试验
    }

    public long getTime() {
        return this.time;
    }

    public void run() {
        this.task.run();
    }
}

//实现一个定时器
class MyTimer {
    //对于定时器中 管理多个任务的，必须使用集合类来进行管理
    //我们使用 —— 优先级队列来管理，我们使用小根堆的规则进行比较，
    //但是我们是自定义的定时器，所以我们需要定义一下比较规则，并且对于比较的类来说要实现比较类的接口
    PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    //多线程，存在线程安全问题，我们进行处理
    private Object locker = new Object();

    //实现 schedule 方法
    //把任务添加到队列中
    //这个是一个线程
    public void schedule(Runnable task,long delay) {
        synchronized (locker) {
            //我们从进行线程执行之后再进行时间上的计数
            //我们这里的时间要取到当时执行这个方法时候的时刻 加上 输入的时间
            MyTimerTask timerTask = new MyTimerTask(task,System.currentTimeMillis() + delay);
            queue.offer(timerTask);
            locker.notify();
        }
    }

    public MyTimer() {
        //这个是第二个线程
        //在构造方法中，额外创建一个线程，用于处理 队列中取出任务进行处理
        Thread t = new Thread(() -> {
            //因为不知道什么时候存在任务，所以我们要对于这个线程的 run 方法进行 true的循环
            try {
                while(true) {
                    synchronized (locker) {
                        //现对其队列进行判断，是否存在 任务，存在就取出进行 对任务的处理
                        //这里当任务没有的时候，会进行 等到但是是忙等，一直在循环的执行，我们要使用 wait notify，进行等待
//                      if(queue.isEmpty()) {
//                          continue;
//                      }
                        while(queue.isEmpty()) {
                            locker.wait();
                        }

                        MyTimerTask task = queue.peek();//查看队首的数据
                        //查看这个任务是否到达了要执行的时间
                        if(task.getTime() > System.currentTimeMillis()) {
                            //这里不用使用while，即使这里被唤醒，也可以在 最外层的while再次判断
                            //说明没有到达要执行的时间，需要进行等到
                            //continue;
                            //这如果使用 continue 的话，就会进行忙等，等待这个时间差，所以这里我们也用 wait
                            //但是这里的 wait 不用 notify 来唤醒，因为不知道何时这个时间差结束
                            //所以对 wait 进行传值
                            locker.wait(task.getTime() - System.currentTimeMillis());
                            //这里不能使用 sleep ，因为sleep不导致锁不能被释放，导致任务进不来
                        }else{
                            //时间到了，我们进行对任务的执行
                            task.run();
                            queue.poll();
                        }
                    }
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
}
public class Demo28 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello myTimer 3000");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello myTimer 2000");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello myTimer 1000");
            }
        },1000);
    }
}
